+ /**
+ * Expand the packageFiles definition into something that's (almost) the right format for
+ * getPackageFiles() to return. This expands shorthands, resolves config vars and callbacks,
+ * but does not expand file paths or read the actual contents of files. Those things are done
+ * by getPackageFiles().
+ *
+ * This is split up in this way so that getFileHashes() can get a list of file names, and
+ * getDefinitionSummary() can get config vars and callback results in their expanded form.
+ *
+ * @param ResourceLoaderContext $context
+ * @return array|null
+ */
+ private function expandPackageFiles( ResourceLoaderContext $context ) {
+ $hash = $context->getHash();
+ if ( isset( $this->expandedPackageFiles[$hash] ) ) {
+ return $this->expandedPackageFiles[$hash];
+ }
+ if ( $this->packageFiles === null ) {
+ return null;
+ }
+ $expandedFiles = [];
+ $mainFile = null;
+
+ foreach ( $this->packageFiles as $alias => $fileInfo ) {
+ // Alias is optional, but only when specfiying plain file names (strings)
+ if ( is_int( $alias ) ) {
+ if ( is_array( $fileInfo ) ) {
+ $msg = __METHOD__ . ": invalid package file definition for module " .
+ "\"{$this->getName()}\": key is required when value is not a string";
+ wfDebugLog( 'resourceloader', $msg );
+ throw new MWException( $msg );
+ }
+ $alias = $fileInfo;
+ }
+ if ( !is_array( $fileInfo ) ) {
+ $fileInfo = [ 'file' => $fileInfo ];
+ }
+
+ // Infer type from alias if needed
+ $type = $fileInfo['type'] ?? self::getPackageFileType( $alias );
+ $expanded = [ 'type' => $type ];
+ if ( !empty( $fileInfo['main'] ) ) {
+ $mainFile = $alias;
+ if ( $type !== 'script' ) {
+ $msg = __METHOD__ . ": invalid package file definition for module " .
+ "\"{$this->getName()}\": main file \"$mainFile\" must be of type \"script\", not \"$type\"";
+ wfDebugLog( 'resourceloader', $msg );
+ throw new MWException( $msg );
+ }
+ }
+
+ if ( isset( $fileInfo['content'] ) ) {
+ $expanded['content'] = $fileInfo['content'];
+ } elseif ( isset( $fileInfo['file'] ) ) {
+ $expanded['filePath'] = $fileInfo['file'];
+ } elseif ( isset( $fileInfo['callback'] ) ) {
+ if ( is_callable( $fileInfo['callback'] ) ) {
+ $expanded['content'] = $fileInfo['callback']( $context );
+ } else {
+ $msg = __METHOD__ . ": invalid callback for package file \"$alias\"" .
+ " in module \"{$this->getName()}\"";
+ wfDebugLog( 'resourceloader', $msg );
+ throw new MWException( $msg );
+ }
+ } elseif ( isset( $fileInfo['config'] ) ) {
+ if ( $type !== 'data' ) {
+ $msg = __METHOD__ . ": invalid use of \"config\" for package file \"$alias\" in module " .
+ "\"{$this->getName()}\": type must be \"data\" but is \"$type\"";
+ wfDebugLog( 'resourceloader', $msg );
+ throw new MWException( $msg );
+ }
+ $expandedConfig = [];
+ foreach ( $fileInfo['config'] as $key => $var ) {
+ $expandedConfig[ is_numeric( $key ) ? $var : $key ] = $this->getConfig()->get( $var );
+ }
+ $expanded['content'] = $expandedConfig;
+ } elseif ( !empty( $fileInfo['main'] ) ) {
+ // 'foo.js' => [ 'main' => true ] is shorthand
+ $expanded['filePath'] = $alias;
+ } else {
+ $msg = __METHOD__ . ": invalid package file definition for \"$alias\" in module " .
+ "\"{$this->getName()}\": one of \"file\", \"content\", \"callback\" or \"config\" must be set";
+ wfDebugLog( 'resourceloader', $msg );
+ throw new MWException( $msg );
+ }
+
+ $expandedFiles[$alias] = $expanded;
+ }
+
+ if ( $expandedFiles && $mainFile === null ) {
+ // The first package file that is a script is the main file
+ foreach ( $expandedFiles as $path => &$file ) {
+ if ( $file['type'] === 'script' ) {
+ $mainFile = $path;
+ break;
+ }
+ }
+ }
+
+ $result = [
+ 'main' => $mainFile,
+ 'files' => $expandedFiles
+ ];
+
+ $this->expandedPackageFiles[$hash] = $result;
+ return $result;
+ }
+
+ /**
+ * Resolves the package files defintion and generates the content of each package file.
+ * @param ResourceLoaderContext $context
+ * @return array Package files data structure, see ResourceLoaderModule::getScript()
+ */
+ public function getPackageFiles( ResourceLoaderContext $context ) {
+ if ( $this->packageFiles === null ) {
+ return null;
+ }
+ $expandedPackageFiles = $this->expandPackageFiles( $context );
+
+ // Expand file contents
+ foreach ( $expandedPackageFiles['files'] as &$fileInfo ) {
+ if ( isset( $fileInfo['filePath'] ) ) {
+ $localPath = $this->getLocalPath( $fileInfo['filePath'] );
+ if ( !file_exists( $localPath ) ) {
+ $msg = __METHOD__ . ": package file not found: \"$localPath\"" .
+ " in module \"{$this->getName()}\"";
+ wfDebugLog( 'resourceloader', $msg );
+ throw new MWException( $msg );
+ }
+ $content = $this->stripBom( file_get_contents( $localPath ) );
+ if ( $fileInfo['type'] === 'data' ) {
+ $content = json_decode( $content );
+ }
+ $fileInfo['content'] = $content;
+ unset( $fileInfo['filePath'] );
+ }
+ }
+
+ return $expandedPackageFiles;
+ }
+